package anaydis.animation.core;

import javax.swing.*;
import java.awt.*;

public abstract class AlgorithmAnimation
        implements Runnable
{
    private Thread thread;

    protected static Color BLUE = new Color(15, 20, 170);
    protected static Color LIGHT_BLUE = new Color(50, 150, 205);
    protected static Color RED = new Color(170, 20, 20);
    protected static Color LIGHT_RED = new Color(200, 100, 100);
    protected static Color GREEN = new Color(15, 170, 20);
    protected static Color LIGHT_GREEN = new Color(50, 205, 150);
    protected static Color MAIN = new Color(100, 150, 200);
    protected static Color LIGHT_MAIN = new Color(180, 200, 220);
    protected static Color TMAIN = new Color(100, 150, 200, 100);
    protected static Color LIGHT_TMAIN = new Color(180, 200, 220, 100);

    protected static final Stroke normalStroke = new BasicStroke(2);

    protected static int[] speedScale = {
        5000, 1000, 750, 500, 300, 200, 150,
        100, 90, 75, 50, 30, 20, 15,
        10, 9, 7, 5, 3, 2, 1 };

    static
    {
        assert speedScale.length == AnimationController.MAX_SPEED + 1 : "Bad Speed scale array";
    }

    protected AnimationController controller;

    protected int speed = 1;

    protected boolean animating;
    protected boolean paused;
    protected boolean stopRequested;

    private boolean painting;
    private boolean waitForPaintingToEnd;

    public void run()
    {
        try {
            doRun();
            controller.repaint();
        }
        catch (StopRequestedException ignore)
        {
        }
        catch (Exception exception)
        {
            JOptionPane.showMessageDialog(null, "Stacktrace exception printed to console!", "An error ocurred during sorting execution!",
                    JOptionPane.ERROR_MESSAGE);

            exception.printStackTrace();
        }

        controller.end();
        thread = null;
    }

    protected abstract void doRun();

    public void start()
    {
        if (thread == null)
        {
            thread = new Thread(this);
            thread.start();
        }
    }

    public void doStop()
    {
        stopRequested = true;
    }

    public void doSleep(int time)
    {
        if (stopRequested)
        {
            throw new StopRequestedException();
        }

        beginPainting();
        controller.repaint();
        doWait(time);
    }

    /**
     * @param time: microseconds at fastest speed, milliseconds at slowest
     */
    private synchronized void doWait(int time)
    {
        try {
            if (paused) {
                wait();
            }
            else if (speed == 0 && !animating) {
                controller.pause();
                paused = true;
                wait();
            }
            else {
                int timeout = (time * speedScale[speed]) / 1000;
                if (timeout <= 0)
                    timeout = 1;
                wait(timeout);
               if (timeout > 20) {
                    while (painting) {
                        waitForPaintingToEnd = true;
                        wait();
                        waitForPaintingToEnd = false;
                    }
                }
            }
        }
        catch (InterruptedException ignored) {
        }
    }

    private void beginPainting()
    {
        painting = true;
    }

    protected synchronized void finishPainting()
    {
        painting = false;
        if (waitForPaintingToEnd) {
            notify();
        }
    }

    synchronized public void pause()
    {
        paused = !paused;
        if (!paused) {
            notify();
        }
    }

    synchronized public void step()
    {
        notify();
    }

    public void setSpeed(int speed)
    {
        if (speed < 0 || speed > AnimationController.MAX_SPEED) {
            throw new IllegalArgumentException("Invalid speed! : " + speed);
        }

        this.speed = speed;
    }

    public static final class StopRequestedException
        extends RuntimeException
    {
    }
}